#include <assert.h>
#include <string.h>

#include "lj_ringbuffer.h"

#ifndef __restrict__
#ifdef _MSC_VER
#define __restrict__
#endif /* _MSC_VER */
#endif /* !__restrict__ */

volatile void* lj_ringbuffer_buffer_copy(volatile void* __restrict__ dest, const volatile void* __restrict__ src, size_t _size)
{
    volatile uint8_t* d = (volatile uint8_t*)dest;
    const volatile uint8_t* s = (const volatile uint8_t*)src;

    for (size_t i = 0; i < _size; i++) {
        d[i] = s[i];
    }

    return dest;
}

volatile void* lj_ringbuffer_buffer_set(volatile void* __restrict__ dest, int val, size_t _size)
{
    volatile uint8_t* d = (volatile uint8_t*)dest;

    for (size_t i = 0; i < _size; i++) {
        d[i] = val;
    }

    return dest;
}

int lj_ring_buffer_sync_init(lj_ringbuffer_sync_t* rb_sync, BUFFER_TYPE* pool, size_t pool_size)
{
    rb_sync->buffer = pool;
    rb_sync->buffer_size = pool_size;

    rb_sync->rb_r_index = 0;
    rb_sync->rb_w_index = 0;
    rb_sync->rb_r_mirror = 0;
    rb_sync->rb_w_mirror = 0;

    int ret = 0;

    if ((ret = pthread_mutex_init(&rb_sync->rbs_lock, NULL)) != 0)
        return ret;

    return ret;
}

size_t lj_ring_buffer_sync_push(lj_ringbuffer_sync_t* rb_sync, const BUFFER_TYPE* src, size_t length)
{
    pthread_mutex_lock(&rb_sync->rbs_lock);

    size_t reserve = 0;
    if (rb_sync->rb_r_mirror == rb_sync->rb_w_mirror) {
        assert(rb_sync->rb_r_index <= rb_sync->rb_w_index);
        reserve = rb_sync->buffer_size - rb_sync->rb_w_index;
        if (reserve >= length) {
            lj_ringbuffer_buffer_copy(&rb_sync->buffer[rb_sync->rb_w_index], src, BUFFER_SIZEOF(length));
            rb_sync->rb_w_index += length;
            assert(rb_sync->rb_w_index <= rb_sync->buffer_size);
        } else if (reserve + rb_sync->rb_r_index >= length) {
            lj_ringbuffer_buffer_copy(&rb_sync->buffer[rb_sync->rb_w_index], src, BUFFER_SIZEOF(reserve));

            lj_ringbuffer_buffer_copy(rb_sync->buffer, &src[reserve], BUFFER_SIZEOF(length - reserve));

            rb_sync->rb_w_index = (length - reserve);
            assert(rb_sync->rb_w_index <= rb_sync->buffer_size);
            rb_sync->rb_w_mirror = ~rb_sync->rb_w_mirror;
        } else
            length = 0;

    } else {
        assert(rb_sync->rb_r_index >= rb_sync->rb_w_index);
        reserve = rb_sync->rb_r_index - rb_sync->rb_w_index;

        if (reserve >= length) {
            lj_ringbuffer_buffer_copy(rb_sync->buffer, src, BUFFER_SIZEOF(length));
            rb_sync->rb_w_index += length;
            assert(rb_sync->rb_w_index <= rb_sync->buffer_size);
        } else
            length = 0;
    };
    assert(rb_sync->rb_r_index <= rb_sync->buffer_size);

    pthread_mutex_unlock(&rb_sync->rbs_lock);

    return length;
}
size_t lj_ring_buffer_sync_pull(lj_ringbuffer_sync_t* rb_sync, BUFFER_TYPE* dest, size_t length)
{
    pthread_mutex_lock(&rb_sync->rbs_lock);

    size_t stock = 0;

    if (rb_sync->rb_r_mirror == rb_sync->rb_w_mirror) {
        assert(rb_sync->rb_r_index <= rb_sync->rb_w_index);
        stock = rb_sync->rb_w_index - rb_sync->rb_r_index;
        if (stock >= length) {
            lj_ringbuffer_buffer_copy(dest, &rb_sync->buffer[rb_sync->rb_r_index], BUFFER_SIZEOF(length));
            lj_ringbuffer_buffer_set(&rb_sync->buffer[rb_sync->rb_r_index], 0, BUFFER_SIZEOF(length));
            rb_sync->rb_r_index += length;
        } else
            length = 0;
    } else {
        stock = rb_sync->buffer_size - rb_sync->rb_r_index;
        if (stock >= length) {
            assert(rb_sync->rb_r_index >= rb_sync->rb_w_index);
            lj_ringbuffer_buffer_copy(dest, &rb_sync->buffer[rb_sync->rb_r_index], BUFFER_SIZEOF(length));
            lj_ringbuffer_buffer_set(&rb_sync->buffer[rb_sync->rb_r_index], 0, BUFFER_SIZEOF(length));
            rb_sync->rb_r_index += length;
        } else if (stock + rb_sync->rb_w_index > length) {
            assert(rb_sync->rb_r_index >= rb_sync->rb_w_index);
            lj_ringbuffer_buffer_copy(dest, &rb_sync->buffer[rb_sync->rb_r_index], stock);
            lj_ringbuffer_buffer_set(&rb_sync->buffer[rb_sync->rb_r_index], 0, BUFFER_SIZEOF(stock));

            lj_ringbuffer_buffer_copy(&dest[stock], rb_sync->buffer, length - stock);
            lj_ringbuffer_buffer_set(&rb_sync->buffer[rb_sync->rb_r_index], 0, BUFFER_SIZEOF(length - stock));

            rb_sync->rb_r_index = length - stock;
            rb_sync->rb_r_mirror = ~rb_sync->rb_r_mirror;
        } else
            length = 0;
    }

    assert(rb_sync->rb_r_index <= rb_sync->buffer_size);
    assert(rb_sync->rb_w_index <= rb_sync->buffer_size);

    pthread_mutex_unlock(&rb_sync->rbs_lock);

    return length;
}
size_t lj_ring_buffer_sync_trypush(lj_ringbuffer_sync_t* rb_sync, const BUFFER_TYPE* src, size_t length);
size_t lj_ring_buffer_sync_trypull(lj_ringbuffer_sync_t* rb_sync, BUFFER_TYPE* dest, size_t length);
